package com.advert.security;

import com.advert.exception.AuthenticationException;
import com.advert.exception.SecurityViolationException;
import com.advert.model.OwnedEntityBase;
import com.advert.model.security.*;
import com.advert.model.security.Role;
import com.advert.service.security.SecurityService;
import com.advert.util.Event;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.*;
import org.jboss.seam.log.Log;

import java.io.Serializable;
import java.util.Map;

@Name("SecurityManager")
@Scope(ScopeType.SESSION)
@AutoCreate
public class AppSecurityManager implements Serializable {
    public static final String DENIED = "DENIED";
    public static final String PERMITTED = "PERMITTED";

    @Logger
    private Log logger;

    @In("SecurityService")
    private SecurityService securityService;
    @In("securityCollection")
    private Map<String, Map<Role, Map<AccessType,SecurityEntity>>> securityCollection;

    private UserEntity user;


    public boolean isLoggedIn() {
        return (user != null);
    }

    public boolean login(Credentials credentials) throws AuthenticationException {
        boolean retVal = false;
        user = securityService.login(credentials);
        retVal = true;
        
        return retVal;
    }

    public void logout() {
        user = null;
    }

    public boolean activateUser(String activatedUserId, String activationCode) throws AuthenticationException {
        boolean retVal = false;
        if (activatedUserId != null && !activatedUserId.isEmpty() && activationCode != null && !activationCode.isEmpty()) {
            try {
                Integer id = Integer.parseInt(activatedUserId);
                user = securityService.activateUser(id, activationCode);
                retVal = true;
            } catch (NumberFormatException e) {
                throw new AuthenticationException(AuthenticationException.Reason.ACTIVATION_INVALID);
            }
        }
        return retVal;
    }

    public boolean registerUser(Credentials credentials) throws AuthenticationException {
        boolean retVal = false;
        securityService.registerUser(credentials);
        retVal = true;

        return retVal;
    }

    public void changePassword(Credentials credentials) throws AuthenticationException {
        if (isLoggedIn()) {
            securityService.changePassword(userId(), credentials);
        } else {
            String message = "Trying change password by not authenticated user.";
            logger.warn(message);
            throw new SecurityViolationException(message);
        }
    }

    public void recoverPassword(Credentials credentials) throws AuthenticationException {
        securityService.recoverPassword(credentials);
    }

    public boolean isUserInRole(String... roles) {
        boolean retVal = false;
        if (user != null && user.getRole() != null) {
            for (String role : roles) {
                if (user.getRole().name().equals(role)) {
                    retVal = true;
                    break;
                }
            }
        }
        return retVal;
    }

    public Integer userId() {
        Integer retVal = null;
        if (isLoggedIn()) {
            retVal = user.getId();
        }
        return retVal;
    }

    public String userLogin() {
        return user.getLogin();
    }

    public String checkBaseAccess(String entity, String accessMode) {
        String retVal = DENIED;
        if (hasBaseAccess(entity, accessMode)) {
            retVal = PERMITTED;
        }
        return retVal;
    }

    public boolean hasBaseAccess(String entity, String accessMode) {
        boolean retVal = false;
        if (isLoggedIn()) {
            if (entity != null && !entity.isEmpty() && accessMode != null && !accessMode.isEmpty()) {
                AccessMode mode = AccessMode.valueOf(accessMode);
                if (securityCollection.get(entity) != null && securityCollection.get(entity).get(user.getRole()) != null) {
                    for (SecurityEntity security : securityCollection.get(entity).get(user.getRole()).values()) {
                        if (security != null && accessPermitted(mode, security)) {
                            retVal = true;
                            break;
                        }
                    }
                }    
            }
        }
        return retVal;
    }

    public boolean hasMasterAccess(String entity, String accessMode) {
        boolean retVal = false;
        if (isLoggedIn()) {
            if (entity != null && !entity.isEmpty() && accessMode != null && !accessMode.isEmpty()) {
                AccessMode mode = AccessMode.valueOf(accessMode);
                if (securityCollection.get(entity) != null && securityCollection.get(entity).get(user.getRole()) != null) {
                    SecurityEntity security = securityCollection.get(entity).get(user.getRole()).get(AccessType.MASTER);
                    if (security != null && accessPermitted(mode, security)) {
                        retVal = true;
                    }
                }
            }
        }
        return retVal;
    }

    @Observer(Event.ENTITY_ACCESS)
    public void entityAccess(OwnedEntityBase ownedEntity, AccessMode mode) {
        String entityName = ownedEntity.getClass().getName();
        String accessMode = mode.name();

        if (!hasMasterAccess(entityName, accessMode)) {
            if (!hasBaseAccess(entityName, accessMode)) {
                String message = "Trying access " + entityName + " in " + accessMode + " mode with no base access rights.";
                logger.warn(message);
                throw new SecurityViolationException(message);
            } else if (ownedEntity.getOwnerId() != null && !userId().equals(ownedEntity.getOwnerId())) {
                String message = "Trying access not owned " + entityName + " in " + accessMode + " mode.";
                logger.warn(message);
                throw new SecurityViolationException(message);                
            }
        }
    }

    @Observer(Event.CLASS_ACCESS)
    public void entityAccess(Class clazz, AccessMode mode) {
        String entityName = clazz.getName();
        String accessMode = mode.name();

        if (!hasMasterAccess(entityName, accessMode)) {
            String message = "Trying access " + entityName + " in " + accessMode + " mode with no master access rights.";
            logger.warn(message);
            throw new SecurityViolationException(message);
        }
    }

    
    private boolean accessPermitted(AccessMode mode, SecurityEntity security) {
        boolean retVal = false;
        switch (mode) {
            case ADD:
                if (security.isAdd()) {
                    retVal = true;
                    break;
                }
            case EDIT:
                if (security.isEdit()) {
                    retVal = true;
                    break;
                }
            case DELETE:
                if (security.isDelete()) {
                    retVal = true;
                    break;
                }
            case VIEW:
                if (security.isView()) {
                    retVal = true;
                    break;
                }
            case ADMIN:
                if (security.isAdmin()) {
                    retVal = true;
                    break;
                }
        }
        return retVal;
    }

}
